package com.system.boss.openssl.service;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import com.system.boss.openssl.model.SysOpenssl;
import com.system.core.utils.ProcessExecutor;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class OpenSSLGenTool {
	
	public static String homedir;
	private SimpleDateFormat usformat = new SimpleDateFormat("MMM dd HH:mm:ss yyyy z",Locale.US);
	
	
	static {
		if(System.getProperty("os.name").toLowerCase().indexOf("windows")>=0) {
			homedir = "d:/cer/";
		} else if(System.getProperty("os.name").toLowerCase().indexOf("linux")>=0) {
			homedir = "/home/cer/";
		} else {
			log.error("未知系统：{}",System.getProperty("os.name").toLowerCase());
		}
	}
	
	public String version() throws Exception {
		return excutor("openssl version -a").replace("\n", "<br>");
	}
	
	
	public void makeRootCA(SysOpenssl sysOpenssl) throws Exception {
		
		if(System.getProperty("os.name").toLowerCase().indexOf("linux")>=0) {
			homedir = "/home/cer/";
//			String sed2 = "sed -i 's@stateOrProvinceName\\s*=\\s*match@stateOrProvinceName     = optional@g\" /usr/lib/ssl/openssl.cnf";
			
			excutor("sed -i 's@dir\\s*=\\s*\\S*@dir             = "+homedir+"@g' /etc/ssl/openssl.cnf");
//			excutor(sed2);
		}
		
		String caPrivateKeyFileName = makeKey(sysOpenssl);
		
		// 生成证书
		StringBuilder caCert = new StringBuilder("openssl req -new -x509 -utf8 -subj ");
		
		// SUBJECT
		if(StringUtils.isNotBlank(sysOpenssl.getCommonName())) {
			caCert.append("/CN="+sysOpenssl.getCommonName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getCountryName())) {
			caCert.append("/C="+sysOpenssl.getCountryName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getStateOrProvinceName())) {
			caCert.append("/ST="+sysOpenssl.getStateOrProvinceName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getStreetAddress())) {
			caCert.append("/STREET="+sysOpenssl.getStreetAddress());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getLocalityName())) {
			caCert.append("/L="+sysOpenssl.getLocalityName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getCompany())) {
			caCert.append("/O="+sysOpenssl.getCompany());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getDepartment())) {
			caCert.append("/OU="+sysOpenssl.getDepartment());
		}
		
		caCert.append(" -passin pass:%s -days %d -key "+homedir+"%s -out "+homedir+"%s");
		
		
		String caCertFileName = genFileName()+".crt";
		String cmd = String.format(caCert.toString(), 
				sysOpenssl.getCerPasswd(),
				sysOpenssl.getDays(),
				caPrivateKeyFileName,caCertFileName);
		log.debug(cmd);
		excutor(cmd);
		sysOpenssl.setCerPath(caCertFileName);
		sysOpenssl.setCerSerial(getCerSerial(sysOpenssl.getCerPath()));
		
		makeCerFinger(sysOpenssl);
		
		makePKCS12(sysOpenssl);
		
		setCerActivityDay(sysOpenssl);
		
		testFileExist(homedir+caCertFileName);
		

	}
	
	public void makeSubCA(SysOpenssl sysOpenssl,SysOpenssl ca) {
		// 生成私钥
		String subCAPrivateKeyFileName = makeKey(sysOpenssl);
		
		// 生成证书请求
		String subCACertReqFileName = makeCerReq(sysOpenssl,subCAPrivateKeyFileName);
		
		// 生成证书
		String subCACertFileName = genFileName()+".crt";
		String subCACert = "openssl ca -extensions v3_ca -in "+homedir+subCACertReqFileName+" -passin pass:"+ca.getCerPasswd()+" -days %d "
				+ "-cert "+homedir+ca.getCerPath()+" -keyfile "+ca.getKeyPath()+" -out "+homedir+subCACertFileName+" -batch";
		String cmd = String.format(subCACert, sysOpenssl.getDays());
		log.debug(cmd);
		excutor(cmd);
		sysOpenssl.setCerPath(subCACertFileName);
		sysOpenssl.setCerSerial(getCerSerial(sysOpenssl.getCerPath()));
		
		makeCerFinger(sysOpenssl);
		makePKCS12(sysOpenssl);
		setCerActivityDay(sysOpenssl);
		testFileExist(homedir+subCACertFileName);
		
		// 清理证书请求
		File subCACertReqFile = new File (homedir,subCACertReqFileName);
		if(subCACertReqFile.exists())
			subCACertReqFile.delete();
	}
	
	public void makeUserCer(SysOpenssl user,SysOpenssl ca) {
		String keyfile = makeKey(user);
		
		String reqfile = makeCerReq(user,keyfile);
		
		makeClientCer(user,reqfile,ca);
		
		makeCerFinger(user);
		
		makePKCS12(user);
		
		setCerActivityDay(user);
		
		user.setCerSerial(getCerSerial(user.getCerPath()));
		
		File reqfiles = new File(homedir,reqfile);
		if(reqfiles.exists())
			reqfiles.delete();
	}
	
	private String makeKey(SysOpenssl sysOpenssl) {
		String privateKey = "openssl genrsa -des3 -passout pass:%s -out "+homedir+"%s 2048";
		String keyFileName = genFileName()+".pem";
		String cmd = String.format(privateKey, sysOpenssl.getCerPasswd(),keyFileName);
		log.debug(cmd);
		excutor(cmd);
		sysOpenssl.setKeyPath(keyFileName);
		
		String privateKeyWithoutPasswd = "openssl rsa -in "+homedir+"%s -out "+homedir+"%s -passin pass:%s";
		String keyNoPasswdFileName = genFileName()+".pem";
		cmd = String.format(privateKeyWithoutPasswd, keyFileName,keyNoPasswdFileName,sysOpenssl.getCerPasswd());
		log.debug(cmd);
		excutor(cmd);
		sysOpenssl.setKeyWithoutPasswdPath(keyNoPasswdFileName);
		return keyFileName;
	}
	
	private String makeCerReq(SysOpenssl sysOpenssl,String keyFileName) {
		String cerReqFileName = genFileName()+".csr";
		StringBuilder subCACertReq = new StringBuilder("openssl req -new -passin pass:"+sysOpenssl.getCerPasswd()+" -utf8 -subj ");
		
		// SUBJECT
		if(StringUtils.isNotBlank(sysOpenssl.getCommonName())) {
			subCACertReq.append("/CN="+sysOpenssl.getCommonName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getCountryName())) {
			subCACertReq.append("/C="+sysOpenssl.getCountryName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getStateOrProvinceName())) {
			subCACertReq.append("/ST="+sysOpenssl.getStateOrProvinceName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getStreetAddress())) {
			subCACertReq.append("/STREET="+sysOpenssl.getStreetAddress());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getLocalityName())) {
			subCACertReq.append("/L="+sysOpenssl.getLocalityName());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getCompany())) {
			subCACertReq.append("/O="+sysOpenssl.getCompany());
		}
		if(StringUtils.isNotBlank(sysOpenssl.getDepartment())) {
			subCACertReq.append("/OU="+sysOpenssl.getDepartment());
		}
		
		subCACertReq.append(" -key "+homedir+keyFileName+" -sha512 -out "+homedir+cerReqFileName);
		subCACertReq.append(" -addext extendedKeyUsage=serverAuth");
		
		
		log.debug(subCACertReq.toString());
		excutor(subCACertReq.toString());
		return cerReqFileName;
	}
	
	private String makeClientCer(SysOpenssl sysOpenssl,String csr,SysOpenssl cacer) {
		String clientCerFileName = genFileName()+".crt";
		
		StringBuilder clientCer = new StringBuilder("openssl x509 -req");
		clientCer.append(" -in "+homedir+csr);
		clientCer.append(" -CA "+homedir+cacer.getCerPath()+" -CAkey "+homedir+cacer.getKeyPath());
		clientCer.append(" -passin pass:"+cacer.getCerPasswd()+" -CAcreateserial");
		clientCer.append(" -out "+homedir+clientCerFileName+" -days "+sysOpenssl.getDays());
		
		sysOpenssl.setCerPath(clientCerFileName);
		log.debug(clientCer.toString());
		excutor(clientCer.toString());
		return clientCerFileName;
	}
	
	private void makeCerFinger(SysOpenssl sysOpenssl) {
		Pattern fingerPattern = Pattern.compile(".*=(.*:.*)",Pattern.CASE_INSENSITIVE);
		String cerfinger = "openssl x509 -fingerprint -noout -in %s";
		String tmp = excutor(String.format(cerfinger, homedir+sysOpenssl.getCerPath()));
		sysOpenssl.setWorkDir(homedir);
//		try {
//			String tmp = IOUtils.toString(process.getInputStream(),"utf-8");
			Matcher matcher1 = fingerPattern.matcher(tmp);
			if(matcher1.find()) {
				sysOpenssl.setCerFinger(matcher1.group(1));
			}
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
	}
	
	public String getCerSerial(String cerPath) {
		Pattern fingerPattern = Pattern.compile("serial=(.*)",Pattern.CASE_INSENSITIVE);
		String cerfinger = "openssl x509 -in "+homedir+cerPath+" -noout -serial";
		String tmp = excutor(cerfinger);
//		try {
//			String tmp = IOUtils.toString(process.getInputStream(),"utf-8");
			Matcher matcher1 = fingerPattern.matcher(tmp);
			log.debug(tmp);
			if(matcher1.find()) {
				return matcher1.group(1);
			} else {
				return null;
			}
//		} catch (IOException e) {
//			e.printStackTrace();
//			return null;
//		}
	}
	
	/**
	 * 吊销证书
	 * @param revokeCer
	 * @param ca
	 */
	public void cerRevoke(String cerpath,SysOpenssl ca) {
		StringBuilder revoke = new StringBuilder("openssl ca");
		revoke.append(" -revoke "+cerpath);
		revoke.append(" -cert "+homedir+ca.getCerPath()+" -keyfile "+homedir+ca.getKeyWithoutPasswdPath());
		revoke.append(" -crl_reason superseded");
		log.debug(revoke.toString());
		excutor(revoke.toString());
	}
	
	/**
	 * 制作CRL
	 * @param ca
	 * @return CRL文件名
	 */
	public String makeCrl(SysOpenssl ca) {
		
		File crlfile = new File(homedir+"crl.crl");
		if(crlfile.exists()) {
			crlfile.delete();
		}
		
		StringBuilder crl = new StringBuilder("openssl ca -gencrl");
		crl.append(" -cert "+homedir+ca.getCerPath()+" -keyfile "+homedir+ca.getKeyWithoutPasswdPath());
		crl.append(" -crldays 365 -out "+homedir+"crl.crl");
		log.info(crl.toString());
		excutor(crl.toString());
		return "crl.crl";
	}
	
	public void setCerActivityDay(SysOpenssl ca) {
		String daystr = "openssl x509 -in "+homedir+ca.getCerPath()+" -noout -dates";
		String tmp = excutor(daystr);
		String[] str = tmp.split("\n");
		
		try {
			String timestart = str[0].split("=")[1];
			ca.setStartTime(usformat.parse(timestart));
			String timeend = str[1].split("=")[1];
			ca.setEndTime(usformat.parse(timeend));
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}
	
	
	private String makePKCS12(SysOpenssl p12) {
		
		String pkc12FileName = genFileName()+".p12";
		StringBuilder pkcs12 = new StringBuilder("openssl pkcs12 -export -clcerts");
		pkcs12.append(" -name \""+p12.getFriendlyName()+"\"");
		pkcs12.append(" -in "+homedir+p12.getCerPath()+" -inkey "+homedir+p12.getKeyPath());
		pkcs12.append(" -passin pass:"+p12.getCerPasswd()+" -passout pass:"+p12.getCerPasswd());
		pkcs12.append(" -out "+homedir+pkc12FileName);
		
		p12.setP12Path(pkc12FileName);
		excutor(pkcs12.toString());
		return pkc12FileName;
	}
	
	public String extractCerFromP12(String p12Path,String passwd) {
		String tmp = genFileName()+".cer";
		String extract = "openssl pkcs12 -clcerts -passin pass:"+passwd+" -nokeys -in "+p12Path+" -out  "+homedir+tmp;
		
		excutor(extract);
		return tmp;
	}
	
	public String makeCerFromPKCS12(SysOpenssl sysOpenssl) {
		
		String pkc12CerFileName = genFileName()+".crt";
		
		StringBuilder cerFromP12 = new StringBuilder("openssl pkcs12 -clcerts -nokeys");
		cerFromP12.append(" -passin pass:"+sysOpenssl.getCerPasswd());
		cerFromP12.append(" -in "+homedir+sysOpenssl.getP12Path()+" -out "+homedir+pkc12CerFileName);
		
		excutor(cerFromP12.toString());
		
		return pkc12CerFileName;
	}
	
	public Process excutor(String[] cmd) {
		try {
			Process process = Runtime.getRuntime().exec(cmd);
			return process;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public String excutor(String cmd) {
		
		try {
			
			File workspace = new File(homedir);
			if(!workspace.exists()) {
				workspace.mkdirs();
			}
			
			File indexFile = new File(workspace, "index.txt");
			if(!indexFile.exists()) {
				BufferedWriter fwriter = new BufferedWriter(new FileWriter(indexFile));
				fwriter.close();
			}
			File crlnumber = new File(workspace, "crlnumber");
			if(!crlnumber.exists()) {
				BufferedWriter fwriter = new BufferedWriter(new FileWriter(crlnumber));
				fwriter.write("00");
				fwriter.close();
			}
			File serial = new File(workspace, "serial");
			if(!serial.exists()) {
				BufferedWriter fwriter = new BufferedWriter(new FileWriter(serial));
				fwriter.write("00");
				fwriter.close();
			}
			File newcerts = new File(workspace, "newcerts");
			if(!newcerts.exists()) {
				newcerts.mkdirs();
			}
			
			
//			StringBuilder exec = new StringBuilder();
			if(System.getProperty("os.name").toLowerCase().indexOf("windows")>=0) {
//				exec.append("cmd /c ");
				Thread.sleep(200);
				return ProcessExecutor.executeOnceByWin(cmd,homedir);
			} else {
				Thread.sleep(200);
				return ProcessExecutor.executeOnce(cmd,homedir)[1];
			}
//			Process process = Runtime.getRuntime().exec(exec.toString()+cmd, null, new File(homedir));
//			log.debug(IOUtils.toString(process.getInputStream(),"utf-8"));
//			log.error(IOUtils.toString(process.getErrorStream(),"utf-8"));
			
//			return process;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	private String genFileName() {
		return UUID.randomUUID().toString().replace("-", "");
	}
	
	private void testFileExist(String testFilePath) {
//		File testFile = new File(testFilePath);
//		while (!testFile.exists()) {
//			log.debug("文件{}不存在！",testFilePath);
//			try {
//				Thread.sleep(100000);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//		}
	}


	public String getHomedir() {
		return homedir;
	}


	public void setHomedir(String homedir) {
		OpenSSLGenTool.homedir = homedir;
	}
}
